
import java.util.*;

/*
 * 1. This class will have just one object among all the bit torrent threads
 * 2. PeerID read in the contructor
 */

public class PeerData {

	/*
	 * - globalBit Vectors : [peer id][piece count] - Hash Maps is needed to
	 * convert PeerId to peerNumber - PeerNum varies from 0 to n-1
	 */
	private int peerID;
	private int numOfPeers;
	private int totalNumOfPieces;
	private boolean[][] globalFilePieceVector;
	private HashMap<Integer, Integer> peerIDHash = new HashMap<Integer, Integer>();
	private HashMap<Integer, Integer> peerNumHash = new HashMap<Integer, Integer>();

	private int numberOfPreferredNeighbors;
	private int unchokingInterval;
	private int optimisticUnchokingInterval;
	private long fileSize;
	private boolean interestedReceived[];
	private boolean interestedSend[];
	private int downloadSpeed[];
	private Random randomGenerator;
	private boolean shutdown = false;

	/*
	 * To update the choking or enter data into queues interested peers que and
	 * peer whom this peer has sent the ineterested Message
	 */

	/*
	 * The signature has been changed
	 */
	PeerData(FileProperties prop, int peerNumber) {
		initPeerHash(prop.peerIDArray);
		globalFilePieceVector = new boolean[prop.getNumPeers()][prop
				.getTotalNumPieces()];
		this.peerID = prop.peerIDArray[peerNumber];
		this.numOfPeers = prop.peerIDArray.length;
		this.totalNumOfPieces = prop.getTotalNumPieces();
		interestedReceived = new boolean[prop.getNumPeers()];
		interestedSend = new boolean[prop.getNumPeers()];
		downloadSpeed = new int[prop.getNumPeers()];
		if (prop.hasCompleteFile()) {
			initBitFields();
		}
		randomGenerator = new Random();
	}

	/*
	 * PlaceHolder Function
	 */
	public void initPeerHash(int[] peerIDArray) {
		for (int i = 0; i < peerIDArray.length; i++) {
			peerIDHash.put(peerIDArray[i], i);
			peerNumHash.put(i, peerIDArray[i]);
			// System.out.println(peerIDArray[0]);
		}
	}

	/*
	 * Says that you have all the complete file
	 */
	private void initBitFields() {
		for (int i = 0; i < totalNumOfPieces; i++) {
			globalFilePieceVector[peerIDHash.get(peerID)][i] = true;
		}
	}

	/*
	 * This method updates all the bitfields about a certain peer into pur bit
	 * vector
	 */
	public synchronized void updateBitfield(int otherPeerID, byte[] bitField) {
		int count = 0;
		for (int i = 0; i < bitField.length; i++) {
			if ((bitField[i] & 0x80) == 0x80) {
				globalFilePieceVector[peerIDHash.get(otherPeerID)][count] = true;
			}
			count++;
			if ((bitField[i] & 0x40) == 0x40) {
				globalFilePieceVector[peerIDHash.get(otherPeerID)][count] = true;
			}
			count++;
			if ((bitField[i] & 0x20) == 0x20) {
				globalFilePieceVector[peerIDHash.get(otherPeerID)][count] = true;
			}
			count++;
			if ((bitField[i] & 0x10) == 0x10) {
				globalFilePieceVector[peerIDHash.get(otherPeerID)][count] = true;
			}
			count++;
			if ((bitField[i] & 0x08) == 0x08) {
				globalFilePieceVector[peerIDHash.get(otherPeerID)][count] = true;
			}
			count++;
			if ((bitField[i] & 0x04) == 0x04) {
				globalFilePieceVector[peerIDHash.get(otherPeerID)][count] = true;
			}
			count++;
			if ((bitField[i] & 0x02) == 0x02) {
				globalFilePieceVector[peerIDHash.get(otherPeerID)][count] = true;
			}
			count++;
			if ((bitField[i] & 0x01) == 0x01) {
				globalFilePieceVector[peerIDHash.get(otherPeerID)][count] = true;
			}
			count++;
		}
	}

	/*
	 * private void updateBit(int bit){ if (bit >=
	 * globalFilePieceVector[0].length){ // insert an assert System.out.println(
	 * "Update Bit field trying to access more pieces than what exists"); } if
	 * ((bitField[i] & 0x80) == 0x80){
	 * globalFilePieceVector[peerIDHash.get(otherPeerID)][count] = true; }
	 * count++; }
	 */

	/*
	 * This method return true if the other peer has any interesting data with
	 * him
	 */
	public synchronized boolean checkInterested(int otherPeerID) {
		for (int i = 0; i < totalNumOfPieces; i++) {
			if (globalFilePieceVector[peerIDHash.get(peerID)][i] == false
					&& globalFilePieceVector[peerIDHash.get(otherPeerID)][i] == true) {
				return true;
			}
		}
		return false;
	}

	/*
	 * This method returns a piece number which you might be interested from the
	 * other peer
	 */
	public int requestPeice(int otherPeerID) {
		for (int i = 0; i < totalNumOfPieces; i++) {
			if (globalFilePieceVector[peerIDHash.get(peerID)][i] == false
					&& globalFilePieceVector[peerIDHash.get(otherPeerID)][i] == true) {
				return i;
			}
		}
		return -1;
	}

	public int getPeerID() {
		return peerID;
	}

	public void printBitVectors() {
		for (int i = 0; i < numOfPeers; i++) {
			System.out.print(i + ": ");
			for (int j = 0; j < totalNumOfPieces; j++) {
				if (globalFilePieceVector[i][j])
					System.out.print(1 + " ");
				else
					System.out.print(0 + " ");
			}
			System.out.println();
		}
	}

	public synchronized void incrementDownloadSpeed(int peerID) {
		downloadSpeed[peerIDHash.get(peerID)] += 1;
	}

	/*
	 * get top n elements ... do you have peerID, how do you want it
	 */
	public synchronized int getDownloadSpeed(int peerID) {
		return downloadSpeed[peerIDHash.get(peerID)];
	}

	/*
	 * The below 4 functions correspond to interestedSend and interestedReceive!
	 */
	public synchronized void addToInterestSend(int peerID) {
		Integer peerNum = peerIDHash.get(peerID);
		if (peerNum != null) {
			if (!interestedSend[peerNum])
				interestedSend[peerNum] = true;
		}
	}

	public synchronized void addToInterestReceived(int peerID) {
		Integer peerNum = peerIDHash.get(peerID);
		if (peerNum != null) {
			if (!interestedReceived[peerNum])
				interestedReceived[peerNum] = true;
		}
	}

	public synchronized void removeFromInterestReceived(int peerID) {
		Integer peerNum = peerIDHash.get(peerID);
		if (peerNum != null) {
			if (interestedReceived[peerNum])
				interestedReceived[peerNum] = false;
		}
	}

	public synchronized void removeFromInterestSend(int peerID) {
		Integer peerNum = peerIDHash.get(peerID);
		if (peerNum != null) {
			if (interestedSend[peerNum])
				interestedSend[peerNum] = false;
		}
	}

	public synchronized int updateBitfield(int otherPeerID, int pieceNum) {
		int count = 0;
		if (!globalFilePieceVector[peerIDHash.get(otherPeerID)][pieceNum]) {
			globalFilePieceVector[peerIDHash.get(otherPeerID)][pieceNum] = true;

			for (int i = 0; i < totalNumOfPieces; i++) {
				if (!globalFilePieceVector[peerIDHash.get(otherPeerID)][i]) {
					count++;
				}
			}
		} else {
			return -1;
		}

		return count;
	}

	public synchronized int getInterestedPieceNumber(int otherPeerID) {
		// Compare peerID bitfield and otherPeerID bitfield to get required
		// piece numbers
		// Pick a random piece number from the above set and send
		int tempPieceNumArray[] = new int[totalNumOfPieces];
		int count = 0;
		for (int i = 0; i < totalNumOfPieces; i++) {
			if (globalFilePieceVector[peerIDHash.get(peerID)][i] == false
					&& globalFilePieceVector[peerIDHash.get(otherPeerID)][i] == true) {
				tempPieceNumArray[count++] = i;
			}
		}
		Random r = new Random();
		return tempPieceNumArray[r.nextInt(count)];

	}

	public synchronized int getPeerNum(int otherPeerID) {
		return peerIDHash.get(otherPeerID);
	}

	public synchronized int getPeerID(int peerNum) {
		return peerNumHash.get(peerNum);
	}

	public synchronized boolean[] getInterestedSend() {
		return interestedSend;
	}

	public synchronized void setInterestedSend(boolean[] interestedSend) {
		this.interestedSend = interestedSend;
	}

	public synchronized boolean checkForTermination() {
		if (!shutdown) {
			for (int i = 0; i < interestedSend.length; i++) {
				if (interestedSend[i] || interestedReceived[i])
					return false;
			}
			shutdown = true;
		}
		return shutdown;
	}
	
	// Functions for Preferred Neighbor selection
	
	// returns an array with peerNumber of k neighbors who provided highest download speed in last unchoking interval and are interested
	// return array value: -1 for no neighbor (<k interested)
	public synchronized int[] topKNeighbors(int k){
		int[] topKNeighbors = new int[k];
		int index = 0;
		int[] sortedDownloadSpeed = createAndSort(downloadSpeed);
		
		// Get the top K neighbors interested
		for(int peerNum=0; peerNum<numOfPeers && index<k; peerNum++){
			if(interestedReceived[peerNum] && containsInFirstK(sortedDownloadSpeed, downloadSpeed[peerNum], k)){
				topKNeighbors[index++] = peerNum;
			}
		}
		// Set the remaining places to -1: indicates <k neighbors interested
		if(index!=k-1){
			while(index<k)
				topKNeighbors[index++] = -1;
		}
		
		return topKNeighbors;
	}
	
	// helper function
	private boolean containsInFirstK(int array[], int checkMe, int k){
		for(int i=0; i<k; i++){
			if(array[i] == checkMe)
				return true;
		}
		
		return false;
	}
	
	// helper function - creates new array from input array, sorts in descending order and returns new array
	// TODO: check for concurrency on downloadSpeed[]
	private int[] createAndSort(int[] sortMe){
		int[] newArray = new int[sortMe.length];
		for(int i=0; i<sortMe.length; i++){
			newArray[i] = sortMe[i];
		}
		Arrays.sort(newArray);
		// reverse array to bring in descending order
		for (int i=0; i<newArray.length/2; i++){
			int temp = newArray[newArray.length - 1 - i];
			newArray[newArray.length - 1 - i] = newArray[i];
			newArray[i] = temp;
		}
		
		return newArray;
	}
	
	// Reset download of all neighbors but input param to zero
	public synchronized void resetDownloadExcept(int exceptPeerNum){
		for(int i=0; i<downloadSpeed.length; i++){
			if(i != exceptPeerNum)
				downloadSpeed[i] = 0;
		}
	}
	
	// returns peerNum for next guy to be opt. unchoked: who is interested but not in currently unchokedNeighbors
	public synchronized int nextOptUnchokedNeighbor(boolean[] unchokedNeighbors){
		Vector<Integer> notUnchoked = new Vector<Integer>(numOfPeers);
		for(int peerNum=0; peerNum<numOfPeers; peerNum++){
			if(interestedReceived[peerNum] && !unchokedNeighbors[peerNum]){
				notUnchoked.add(peerNum);
			}				
		}
		int random = randomGenerator.nextInt(notUnchoked.size() - 1);
		return (Integer) notUnchoked.get(random);
	}
}
